﻿/**
 * ZSorter
 *
 * @author		Allen Chou
 * @version		1.0.0 (last update: Dec 10 2008)
 * @link        http://cjcat.blogspot.com
 * @link		http://cjcat2266.deviantart.com
 */

package idv.cjcat.display.zsorter {
	import flash.display.*;
	import flash.events.*;

	/**
	 * The <code>ZSorter</code> class lets you sort display objects' order in the display list according to their 3D coordinates.
	 */
	public class ZSorter {
		
		private var _dataVec:Vector.<ZData>;
		private var _data:ZData;
		private var _zFilters:Vector.<IZFilter>;
		private var _zFilter:IZFilter;
		
		/**
		 * The container whose children are to be sorted.
		 */
		public var container:DisplayObjectContainer;
		/**
		 * The reference container, usually the parent of the container whose children are to be sorted. If you have no idea what should be the best reference container, use the stage instance.
		 */
		public var reference:DisplayObjectContainer;
		
		/**
		 * Creates a <code>ZSorter</code> object.
		 * @param	container  The container whose children are to be sorted.
		 * @param	reference  The reference container, usually the parent of the container whose children are to be sorted. If you have no idea what should be the best reference container, use the stage instance.
		 */
		public function ZSorter(container:DisplayObjectContainer, reference:DisplayObjectContainer) {
			this.container = container;
			this.reference = reference;
			_dataVec = new Vector.<ZData>();
			_zFilters = new Vector.<IZFilter>();
		}
		
		/**
		 * Call this method repeatedly to constantly sort the order of children in the container.
		 * <p>This method can be used as an event listener.</p>
		 * <p>If there is one or more z-filters applied, the filter array of every children display object will be replaced with a new one on each call.</p>
		 */
		public function sort(e:Event = null):void {
			if (_dataVec.length < container.numChildren) {
				var diff:int = container.numChildren - _dataVec.length;
				_dataVec.length += diff;
				while (diff-- > 0) {
					_dataVec[_dataVec.length - diff - 1] = new ZData(null, 0);
				}
			} else if (_dataVec.length > container.numChildren) {
				_dataVec.length = container.numChildren;
			}
			
			for (var i:int = 0; i < container.numChildren; i++) {
				_dataVec[i].obj = container.getChildAt(i);
				_dataVec[i].cameraZ = container.getChildAt(i).transform.getRelativeMatrix3D(reference).position.z;
			}
			_dataVec.sort(zComparator);
			for each (_data in _dataVec) {
				container.addChild(_data.obj);
				
				if (_zFilters.length > 0) _data.obj.filters = [];
				
				//process Z-filters
				for each (_zFilter in _zFilters) {
					_zFilter.process(_data);
				}
			}
		}
		
		/**
		 * Adds a z-filter that applies special effects on display objects according to their z-depth value relative to the reference container.
		 * @param	filter  The z-filter.
		 */
		public function addFilter(filter:IZFilter):void {
			_zFilters.push(filter);
		}
		
		/**
		 * Removes a z-filter.
		 * @param	filter  The z-filter.
		 */
		public function removeFilter(filter:IZFilter):void {
			while (_zFilters.indexOf(filter) != -1) _zFilters.splice(_zFilters.indexOf(filter), 1);
		}
		
		/**
		 * Clears all z-filters.
		 */
		public function clearFilters():void {
			_zFilters.length = 0;
		}
		
		private static function zComparator(a:ZData, b:ZData):Number {
			if (a.cameraZ > b.cameraZ) return -1;
			return 1;
		}
		
	}
}